
/*-------------------------------------------------------------------------
 *
 * adminpack.c
 *
 *
 * Copyright (c) 2002-2022, PostgreSQL Global Development Group
 *
 * Author: Andreas Pflug <pgadmin@pse-consulting.de>
 *
 * IDENTIFICATION
 *	  contrib/adminpack/adminpack.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <sys/file.h>
#include <sys/stat.h>
#include <unistd.h>

#include "catalog/pg_authid.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "postmaster/syslogger.h"
#include "storage/fd.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/datetime.h"


#ifdef WIN32

#ifdef rename
#undef rename
#endif

#ifdef unlink
#undef unlink
#endif
#endif

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(pg_file_write);
PG_FUNCTION_INFO_V1(pg_file_write_v1_1);
PG_FUNCTION_INFO_V1(pg_file_sync);
PG_FUNCTION_INFO_V1(pg_file_rename);
PG_FUNCTION_INFO_V1(pg_file_rename_v1_1);
PG_FUNCTION_INFO_V1(pg_file_unlink);
PG_FUNCTION_INFO_V1(pg_file_unlink_v1_1);
PG_FUNCTION_INFO_V1(pg_logdir_ls);
PG_FUNCTION_INFO_V1(pg_logdir_ls_v1_1);

static int64 fc_pg_file_write_internal(text *fc_file, text *fc_data, bool fc_replace);
static bool fc_pg_file_rename_internal(text *fc_file1, text *fc_file2, text *fc_file3);
static Datum fc_pg_logdir_ls_internal(FunctionCallInfo fc_fcinfo);


/*-----------------------
 * 一些助手函数
 */

/*
 * 将“文本”文件名参数转换为 C 字符串，并检查其是否允许。
 *
 * 文件名可以是绝对路径或相对于 DataDir，但我们只允许
 * 匹配 DataDir 的绝对路径。
 */
static char * fc_convert_and_check_filename(text *fc_arg)
{
	char	   *fc_filename = text_to_cstring(fc_arg);

	canonicalize_path(fc_filename);	/* 文件名在这里可以变化长度 */

	/*
	 * 'pg_write_server_files' 角色的成员被允许以 PG 用户的身份访问服务器上的任何
	 * 文件，因此不需要在这里进行进一步的检查。
	 */
	if (has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
		return fc_filename;

	/*
	 * 用户不是 pg_write_server_files 角色的成员，因此检查其是否
	 * 允许。
	 */
	if (is_absolute_path(fc_filename))
	{
		/* 如果在 DataDir 内，则允许绝对路径 */
		if (!path_is_prefix_of_path(DataDir, fc_filename))
			ereport(ERROR,
					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
					 errmsg("absolute path not allowed")));
	}
	else if (!path_is_relative_and_below_cwd(fc_filename))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("path must be in or below the current directory")));

	return fc_filename;
}


/*
 * 检查是否为超级用户，如果不是则发出警告。
 */
static void fc_requireSuperuser(void)
{
	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("only superuser may access generic file functions")));
}



/* ------------------------------------
 * pg_file_write - 旧版本
 *
 * 这里的 superuser() 检查必须保留，因为库可能会在未升级扩展的情况下
 * 进行升级，这意味着在 1.1 之前的安装中，这些函数可能被任何用户调用。
 */
Datum pg_file_write(PG_FUNCTION_ARGS)
{
	text	   *fc_file = PG_GETARG_TEXT_PP(0);
	text	   *fc_data = PG_GETARG_TEXT_PP(1);
	bool		fc_replace = PG_GETARG_BOOL(2);
	int64		fc_count = 0;

	fc_requireSuperuser();

	fc_count = fc_pg_file_write_internal(fc_file, fc_data, fc_replace);

	PG_RETURN_INT64(fc_count);
}

/* ------------------------------------
 * pg_file_write_v1_1 - 版本 1.1
 *
 * 从 adminpack 版本 1.1 开始，我们不再需要检查用户
 * 是否为超级用户，因为我们从 PUBLIC 撤销了对该函数的执行权限。
 * 用户可以根据其策略授予对该函数的访问权限。
 *
 * 其余部分与 pg_file_write（上述）相同。
 */
Datum pg_file_write_v1_1(PG_FUNCTION_ARGS)
{
	text	   *fc_file = PG_GETARG_TEXT_PP(0);
	text	   *fc_data = PG_GETARG_TEXT_PP(1);
	bool		fc_replace = PG_GETARG_BOOL(2);
	int64		fc_count = 0;

	fc_count = fc_pg_file_write_internal(fc_file, fc_data, fc_replace);

	PG_RETURN_INT64(fc_count);
}

/* ------------------------------------
 * pg_file_write_internal - pg_file_write 函数的工作马。
 *
 * 处理 pg_file_write 的实际工作。
 */
static int64 fc_pg_file_write_internal(text *fc_file, text *fc_data, bool fc_replace)
{
	FILE	   *fc_f;
	char	   *fc_filename;
	int64		fc_count = 0;

	fc_filename = fc_convert_and_check_filename(fc_file);

	if (!fc_replace)
	{
		struct stat fc_fst;

		if (stat(fc_filename, &fc_fst) >= 0)
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_FILE),
					 errmsg("file \"%s\" exists", fc_filename)));

		fc_f = AllocateFile(fc_filename, "wb");
	}
	else
		fc_f = AllocateFile(fc_filename, "ab");

	if (!fc_f)
#ifdef FDD //cppcheck
	{
#endif
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not open file \"%s\" for writing: %m",
						fc_filename)));
#ifdef FDD //cppcheck
		return 0;
	}
#endif
	fc_count = fwrite(VARDATA_ANY(fc_data), 1, VARSIZE_ANY_EXHDR(fc_data), fc_f);
	if (fc_count != VARSIZE_ANY_EXHDR(fc_data) || FreeFile(fc_f))
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not write file \"%s\": %m", fc_filename)));

	return (fc_count);
}

/* ------------------------------------
 * pg_file_sync
 *
 * 我们从 PUBLIC 撤销了对该函数的执行权限。
 * 用户可以根据其策略授予对该函数的访问权限。
 */
Datum pg_file_sync(PG_FUNCTION_ARGS)
{
	char	   *fc_filename;
	struct stat fc_fst;

	fc_filename = fc_convert_and_check_filename(PG_GETARG_TEXT_PP(0));

	if (stat(fc_filename, &fc_fst) < 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not stat file \"%s\": %m", fc_filename)));

	fsync_fname_ext(fc_filename, S_ISDIR(fc_fst.st_mode), false, ERROR);

	PG_RETURN_VOID();
}

/* ------------------------------------
 * pg_file_rename - 旧版本
 *
 * 这里的 superuser() 检查必须保留，因为库可能会在未升级扩展的情况下
 * 进行升级，这意味着在 1.1 之前的安装中，这些函数可能被任何用户调用。
 */
Datum pg_file_rename(PG_FUNCTION_ARGS)
{
	text	   *fc_file1;
	text	   *fc_file2;
	text	   *fc_file3;
	bool		fc_result;

	fc_requireSuperuser();

	if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
		PG_RETURN_NULL();

	fc_file1 = PG_GETARG_TEXT_PP(0);
	fc_file2 = PG_GETARG_TEXT_PP(1);

	if (PG_ARGISNULL(2))
		fc_file3 = NULL;
	else
		fc_file3 = PG_GETARG_TEXT_PP(2);

	fc_result = fc_pg_file_rename_internal(fc_file1, fc_file2, fc_file3);

	PG_RETURN_BOOL(fc_result);
}

/* ------------------------------------
 * pg_file_rename_v1_1 - 版本 1.1
 *
 * 从 adminpack 版本 1.1 开始，我们不再需要检查用户
 * 是否为超级用户，因为我们从 PUBLIC 撤销了对该函数的执行权限。
 * 用户可以根据其策略授予对该函数的访问权限。
 *
 * 其余部分与 pg_file_write（上述）相同。
 */
Datum pg_file_rename_v1_1(PG_FUNCTION_ARGS)
{
	text	   *fc_file1;
	text	   *fc_file2;
	text	   *fc_file3;
	bool		fc_result;

	if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
		PG_RETURN_NULL();

	fc_file1 = PG_GETARG_TEXT_PP(0);
	fc_file2 = PG_GETARG_TEXT_PP(1);

	if (PG_ARGISNULL(2))
		fc_file3 = NULL;
	else
		fc_file3 = PG_GETARG_TEXT_PP(2);

	fc_result = fc_pg_file_rename_internal(fc_file1, fc_file2, fc_file3);

	PG_RETURN_BOOL(fc_result);
}

/* ------------------------------------
 * pg_file_rename_internal - pg_file_rename 函数的工作马。
 *
 * 处理 pg_file_rename 的实际工作。
 */
static bool fc_pg_file_rename_internal(text *fc_file1, text *fc_file2, text *fc_file3)
{
	char	   *fc_fn1,
			   *fc_fn2,
			   *fc_fn3;
	int			fc_rc;

	fc_fn1 = fc_convert_and_check_filename(fc_file1);
	fc_fn2 = fc_convert_and_check_filename(fc_file2);

	if (fc_file3 == NULL)
		fc_fn3 = NULL;
	else
		fc_fn3 = fc_convert_and_check_filename(fc_file3);

	if (access(fc_fn1, W_OK) < 0)
	{
		ereport(WARNING,
				(errcode_for_file_access(),
				 errmsg("file \"%s\" is not accessible: %m", fc_fn1)));

		return false;
	}

	if (fc_fn3 && access(fc_fn2, W_OK) < 0)
	{
		ereport(WARNING,
				(errcode_for_file_access(),
				 errmsg("file \"%s\" is not accessible: %m", fc_fn2)));

		return false;
	}

	fc_rc = access(fc_fn3 ? fc_fn3 : fc_fn2, W_OK);
	if (fc_rc >= 0 || errno != ENOENT)
	{
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_FILE),
				 errmsg("cannot rename to target file \"%s\"",
						fc_fn3 ? fc_fn3 : fc_fn2)));
	}

	if (fc_fn3)
	{
		if (rename(fc_fn2, fc_fn3) != 0)
		{
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not rename \"%s\" to \"%s\": %m",
							fc_fn2, fc_fn3)));
		}
		if (rename(fc_fn1, fc_fn2) != 0)
		{
			ereport(WARNING,
					(errcode_for_file_access(),
					 errmsg("could not rename \"%s\" to \"%s\": %m",
							fc_fn1, fc_fn2)));

			if (rename(fc_fn3, fc_fn2) != 0)
			{
				ereport(ERROR,
						(errcode_for_file_access(),
						 errmsg("could not rename \"%s\" back to \"%s\": %m",
								fc_fn3, fc_fn2)));
			}
			else
			{
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_FILE),
						 errmsg("renaming \"%s\" to \"%s\" was reverted",
								fc_fn2, fc_fn3)));
			}
		}
	}
	else if (rename(fc_fn1, fc_fn2) != 0)
	{
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not rename \"%s\" to \"%s\": %m", fc_fn1, fc_fn2)));
	}

	return true;
}


/* ------------------------------------
 * pg_file_unlink - 旧版本
 *
 * 这里的 superuser() 检查必须保留，因为库可能会在未升级扩展的情况下
 * 进行升级，这意味着在 1.1 之前的安装中，这些函数可能被任何用户调用。
 */
Datum pg_file_unlink(PG_FUNCTION_ARGS)
{
	char	   *fc_filename;

	fc_requireSuperuser();

	fc_filename = fc_convert_and_check_filename(PG_GETARG_TEXT_PP(0));

	if (access(fc_filename, W_OK) < 0)
	{
		if (errno == ENOENT)
			PG_RETURN_BOOL(false);
		else
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("file \"%s\" is not accessible: %m", fc_filename)));
	}

	if (unlink(fc_filename) < 0)
	{
		ereport(WARNING,
				(errcode_for_file_access(),
				 errmsg("could not unlink file \"%s\": %m", fc_filename)));

		PG_RETURN_BOOL(false);
	}
	PG_RETURN_BOOL(true);
}


/* ------------------------------------
 * pg_file_unlink_v1_1 - 版本 1.1
 *
 * 从 adminpack 版本 1.1 开始，我们不再需要检查用户
 * 是否是超级用户，因为我们从 PUBLIC 撤销了对该函数的执行权限。
 * 用户可以根据他们的策略授予访问权限。
 *
 * 与 pg_file_unlink（上面）完全相同。
 */
Datum pg_file_unlink_v1_1(PG_FUNCTION_ARGS)
{
	char	   *fc_filename;

	fc_filename = fc_convert_and_check_filename(PG_GETARG_TEXT_PP(0));

	if (access(fc_filename, W_OK) < 0)
	{
		if (errno == ENOENT)
			PG_RETURN_BOOL(false);
		else
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("file \"%s\" is not accessible: %m", fc_filename)));
	}

	if (unlink(fc_filename) < 0)
	{
		ereport(WARNING,
				(errcode_for_file_access(),
				 errmsg("could not unlink file \"%s\": %m", fc_filename)));

		PG_RETURN_BOOL(false);
	}
	PG_RETURN_BOOL(true);
}

/* ------------------------------------
 * pg_logdir_ls - 老版本
 *
 * 此处必须保留 superuser() 检查，因为库可能在未升级扩展的情况下进行升级，
 * 这意味着在 1.1 之前的安装中，这些函数可能会被任何用户调用。
 */
Datum pg_logdir_ls(PG_FUNCTION_ARGS)
{
	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("only superuser can list the log directory")));

	return (fc_pg_logdir_ls_internal(fcinfo));
}

/* ------------------------------------
 * pg_logdir_ls_v1_1 - 版本 1.1
 *
 * 从 adminpack 版本 1.1 开始，我们不再需要检查用户
 * 是否是超级用户，因为我们从 PUBLIC 撤销了对该函数的执行权限。
 * 用户可以根据他们的策略授予访问权限。
 *
 * 与 pg_logdir_ls（上面）完全相同。
 */
Datum pg_logdir_ls_v1_1(PG_FUNCTION_ARGS)
{
	return (fc_pg_logdir_ls_internal(fcinfo));
}

static Datum fc_pg_logdir_ls_internal(FunctionCallInfo fc_fcinfo)
{
	ReturnSetInfo *fc_rsinfo = (ReturnSetInfo *) fc_fcinfo->resultinfo;
	bool		fc_randomAccess;
	TupleDesc	fc_tupdesc;
	Tuplestorestate *fc_tupstore;
	AttInMetadata *fc_attinmeta;
	DIR		   *fc_dirdesc;
	struct dirent *fc_de;
	MemoryContext fc_oldcontext;

	if (strcmp(Log_filename, "postgresql-%Y-%m-%d_%H%M%S.log") != 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("the log_filename parameter must equal 'postgresql-%%Y-%%m-%%d_%%H%%M%%S.log'")));

	/* 检查调用者是否支持我们返回 tupplestore */
	if (fc_rsinfo == NULL || !IsA(fc_rsinfo, ReturnSetInfo))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("set-valued function called in context that cannot accept a set")));
	if (!(fc_rsinfo->allowedModes & SFRM_Materialize))
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("materialize mode required, but it is not allowed in this context")));

	/* tupdesc 和 tuplestore 必须在 ecxt_per_query_memory 中创建 */
	fc_oldcontext = MemoryContextSwitchTo(fc_rsinfo->econtext->ecxt_per_query_memory);

	fc_tupdesc = CreateTemplateTupleDesc(2);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 1, "starttime",
					   TIMESTAMPOID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 2, "filename",
					   TEXTOID, -1, 0);

	fc_randomAccess = (fc_rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
	fc_tupstore = tuplestore_begin_heap(fc_randomAccess, false, work_mem);
	fc_rsinfo->returnMode = SFRM_Materialize;
	fc_rsinfo->setResult = fc_tupstore;
	fc_rsinfo->setDesc = fc_tupdesc;

	MemoryContextSwitchTo(fc_oldcontext);

	fc_attinmeta = TupleDescGetAttInMetadata(fc_tupdesc);

	fc_dirdesc = AllocateDir(Log_directory);
	while ((fc_de = ReadDir(fc_dirdesc, Log_directory)) != NULL)
	{
		char	   *fc_values[2];
		HeapTuple	fc_tuple;
		char		fc_timestampbuf[32];
		char	   *fc_field[MAXDATEFIELDS];
		char		fc_lowstr[MAXDATELEN + 1];
		int			fc_dtype;
		int			fc_nf,
					fc_ftype[MAXDATEFIELDS];
		fsec_t		fc_fsec;
		int			fc_tz = 0;
		struct pg_tm fc_date;

		/*
		 * 默认格式：postgresql-YYYY-MM-DD_HHMMSS.log
		 */
		if (strlen(fc_de->d_name) != 32
			|| strncmp(fc_de->d_name, "postgresql-", 11) != 0
			|| fc_de->d_name[21] != '_'
			|| strcmp(fc_de->d_name + 28, ".log") != 0)
			continue;

		/* 提取文件名的时间戳部分 */
		strcpy(fc_timestampbuf, fc_de->d_name + 11);
		fc_timestampbuf[17] = '\0';

		/* 解析并解码预期的时间戳以验证其格式是否正确 */
		if (ParseDateTime(fc_timestampbuf, fc_lowstr, MAXDATELEN, fc_field, fc_ftype, MAXDATEFIELDS, &fc_nf))
			continue;

		if (DecodeDateTime(fc_field, fc_ftype, fc_nf, &fc_dtype, &fc_date, &fc_fsec, &fc_tz))
			continue;

		/* 看起来时间戳格式正确；准备并返回元组 */

		fc_values[0] = fc_timestampbuf;
		fc_values[1] = psprintf("%s/%s", Log_directory, fc_de->d_name);

		fc_tuple = BuildTupleFromCStrings(fc_attinmeta, fc_values);

		tuplestore_puttuple(fc_tupstore, fc_tuple);
	}

	FreeDir(fc_dirdesc);
	return (Datum) 0;
}
